{
 "cells": [
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "tTuYGOlnh117"
   },
   "source": [
    "#  Working With Splitters\n",
    "\n",
    "When using machine learning, you typically divide your data into training, validation, and test sets.  The MoleculeNet loaders do this automatically.  But how should you divide up the data?  This question seems simple at first, but it turns out to be quite complicated.  There are many ways of splitting up data, and which one you choose can have a big impact on the reliability of your results.  This tutorial introduces some of the splitting methods provided by DeepChem.\n",
    "\n",
    "## Colab\n",
    "\n",
    "This tutorial and the rest in this sequence can be done in Google colab. If you'd like to open this notebook in colab, you can use the following link.\n",
    "\n",
    "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepchem/deepchem/blob/master/examples/tutorials/Working_With_Splitters.ipynb)\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 188
    },
    "colab_type": "code",
    "id": "D43MbibL_EK0",
    "outputId": "e7b205ae-9962-4089-d49a-6d0ebe4c8430"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'2.7.1'"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "!pip install --pre deepchem\n",
    "import deepchem\n",
    "deepchem.__version__"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "omxBgQVDh12B"
   },
   "source": [
    "## Splitters\n",
    "\n",
    "In DeepChem, a method of splitting samples into multiple datasets is defined by a `Splitter` object.  Choosing an appropriate method for your data is very important.  Otherwise, your trained model may seem to work much better than it really does.\n",
    "\n",
    "Consider a typical drug development pipeline.  You might begin by screening many thousands of molecules to see if they bind to your target of interest.  Once you find one that seems to work, you try to optimize it by testing thousands of minor variations on it, looking for one that binds more strongly.  Then perhaps you test it in animals and find it has unacceptable toxicity, so you try more variations to fix the problems.\n",
    "\n",
    "This has an important consequence for chemical datasets: they often include lots of molecules that are very similar to each other.  If you split the data into training and test sets in a naive way, the training set will include many molecules that are very similar to the ones in the test set, even if they are not exactly identical.  As a result, the model may do very well on the test set, but then fail badly when you try to use it on other data that is less similar to the training data.\n",
    "\n",
    "Let's take a look at a few of the splitters found in DeepChem.\n",
    "\n",
    "### • General Splitters\n",
    "      ○ RandomSplitter\n",
    "      ○ RandomGroupSplitter\n",
    "      ○ RandomStratifiedSplitter\n",
    "      ○ SingletaskStratifiedSplitter\n",
    "      ○ IndexSplitter\n",
    "      ○ SpecifiedSplitter\n",
    "      ○ TaskSplitter\n",
    "\n",
    "### • Molecular Splitters\n",
    "      ○ ScaffoldSplitter\n",
    "      ○ MolecularWeightSplitter\n",
    "      ○ MaxMinSplitter\n",
    "      ○ ButinaSplitter\n",
    "      ○ FingerprintSplitter\n",
    "\n",
    "\n",
    " Let's take a look how different splitters work.\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "### RandomSplitter\n",
    "\n",
    "This is one of the simplest splitters.  It just selects samples for the training, validation, and test sets in a completely random way.\n",
    "\n",
    "Didn't we just say that's a bad idea?  Well, it depends on your data.  If every sample is truly independent of every other, then this is just as good a way as any to split the data.  There is no universally best choice of splitter.  It all depends on your particular dataset, and for some datasets this is a fine choice.\n",
    "\n",
    "### RandomStratifiedSplitter\n",
    "\n",
    "Some datasets are very unbalanced: only a tiny fraction of all samples are positive.  In that case, random splitting may sometimes lead to the validation or test set having few or even no positive samples for some tasks.  That makes it unable to evaluate performance.\n",
    "\n",
    "`RandomStratifiedSplitter` addresses this by dividing up the positive and negative samples evenly.  If you ask for a 80/10/10 split, the validation and test sets will contain not just 10% of samples, but also 10% of the positive samples for each task.\n",
    "\n",
    "### ScaffoldSplitter\n",
    "\n",
    "This splitter tries to address the problem discussed above where many molecules are very similar to each other.  It identifies the scaffold that forms the core of each molecule, and ensures that all molecules with the same scaffold are put into the same dataset.  This is still not a perfect solution, since two molecules may have different scaffolds but be very similar in other ways, but it usually is a large improvement over random splitting.\n",
    "\n",
    "### ButinaSplitter\n",
    "\n",
    "This is another splitter that tries to address the problem of similar molecules.  It clusters them based on their molecular fingerprints, so that ones with similar fingerprints will tend to be in the same dataset.  The time required by this splitting algorithm scales as the square of the number of molecules, so it is mainly useful for small to medium sized datasets.\n",
    "\n",
    "### SpecifiedSplitter\n",
    "\n",
    "This splitter leaves everything up to the user.  You tell it exactly which samples to put in each dataset.  This is useful when you know in advance that a particular splitting is appropriate for your data.\n",
    "\n",
    "An example is temporal splitting.  Consider a research project where you are continually generating and testing new molecules.  As you gain more data, you periodically retrain your model on the steadily growing dataset, then use it to predict results for other not yet tested molecules.  A good way of validating whether this works is to pick a particular cutoff date, train the model on all data you had at that time, and see how well it predicts other data that was generated later.\n",
    "\n",
    "### TaskSplitter\n",
    "\n",
    "Provides a simple interface for splitting datasets task-wise.\n",
    "\n",
    "For some learning problems, the training and test datasets should have different tasks entirely. This is a different paradigm from the usual Splitter, which ensures that split datasets have different data points, not different tasks. This method improves multi-task learning and problem decomposition situations by enhancing their efficiency and performance.\n",
    "\n",
    "### SingletaskStratifiedSplitter\n",
    "\n",
    "Another way of splitting data, particularly for classification tasks with imbalanced class distributions is the single-task stratified splitter. The single-task stratified splitter maintains the class distribution in the original dataset across training, validation and test sets. This is crucial when working with imbalanced datasets where some classes may be under-represented.\n",
    "\n",
    "### FingerprintSplitter\n",
    "\n",
    "Class for doing data splits based on the Tanimoto similarity(Tanimoto similarity measures overlap between two sets succinctly) between ECFP4 fingerprints(ECFP4 fingerprints encode unique parts of molecules for efficient comparison).\n",
    "\n",
    "This class tries to split the data such that the molecules in each dataset are as different as possible from the ones in the other datasets. This makes it a very stringent test of models. Predicting the test and validation sets may require extrapolating far outside the training data.It splits molecular datasets using Tanimoto similarity scores calculated from ECFP4 fingerprints. ECFP4, based on Morgan fingerprints, encodes molecular substructures.\n",
    "\n",
    "### MolecularWeightSplitter\n",
    "\n",
    "Another splitter performs data splits based on molecular weight\n",
    "\n",
    "\n",
    "## Effect of Using Different Splitters\n",
    "\n",
    "Let's look at an example.  We will load the Tox21 toxicity dataset using random, fingerprint, scaffold, and Butina splitting.  For each one we train a model and evaluate it on the training and test sets."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "Sp5Hbb4nh12C"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "splitter: random\n",
      "training set score: {'roc_auc_score': 0.9554904185889012}\n",
      "test set score: {'roc_auc_score': 0.7854105497196335}\n",
      "\n",
      "splitter: scaffold\n",
      "training set score: {'roc_auc_score': 0.958752269558084}\n",
      "test set score: {'roc_auc_score': 0.6849149319233084}\n",
      "\n",
      "splitter: butina\n",
      "training set score: {'roc_auc_score': 0.9584914471889929}\n",
      "test set score: {'roc_auc_score': 0.6061155305251504}\n",
      "\n",
      "splitter: fingerprint\n",
      "training set score: {'roc_auc_score': 0.954193849465875}\n",
      "test set score: {'roc_auc_score': 0.6235667313881933}\n",
      "\n"
     ]
    }
   ],
   "source": [
    "import deepchem as dc\n",
    "\n",
    "splitters = ['random', 'scaffold', 'butina', 'fingerprint']\n",
    "metric = dc.metrics.Metric(dc.metrics.roc_auc_score)\n",
    "for splitter in splitters:\n",
    "    tasks, datasets, transformers = dc.molnet.load_tox21(featurizer='ECFP', splitter=splitter)\n",
    "    train_dataset, valid_dataset, test_dataset = datasets\n",
    "    model = dc.models.MultitaskClassifier(n_tasks=len(tasks), n_features=1024, layer_sizes=[1000])\n",
    "    model.fit(train_dataset, nb_epoch=10)\n",
    "    print('splitter:', splitter)\n",
    "    print('training set score:', model.evaluate(train_dataset, [metric], transformers))\n",
    "    print('test set score:', model.evaluate(test_dataset, [metric], transformers))\n",
    "    print()"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "All of them produce very similar performance on the training set, but the random splitter has much higher performance on the test set.  Scaffold splitting has a lower test set score, and Butina splitting is even lower.  Does that mean random splitting is better?  No!  It means random splitting doesn't give you an accurate measure of how well your model works.  Because the test set contains lots of molecules that are very similar to ones in the training set, it isn't truly independent.  It makes the model appear to work better than it really does.  Scaffold splitting and Butina splitting give a better indication of what you can expect on independent data in the future."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "wssi6cBmh12z"
   },
   "source": [
    "# Congratulations! Time to join the Community!\n",
    "\n",
    "Congratulations on completing this tutorial notebook! If you enjoyed working through the tutorial, and want to continue working with DeepChem, we encourage you to finish the rest of the tutorials in this series. You can also help the DeepChem community in the following ways:\n",
    "\n",
    "## Star DeepChem on [GitHub](https://github.com/deepchem/deepchem)\n",
    "This helps build awareness of the DeepChem project and the tools for open source drug discovery that we're trying to build.\n",
    "\n",
    "## Join the DeepChem Discord\n",
    "The DeepChem [discord](https://discord.com/channels/1170082703853490257/1210627997921443870) hosts a number of scientists, developers, and enthusiasts interested in deep learning for the life sciences. Join the conversation!"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {
    "id": "pOBd6-YdQSvF"
   },
   "source": [
    "## Citing This Tutorial\n",
    "If you found this tutorial useful please consider citing it using the provided BibTeX. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "id": "KZUk_9yIYw0c"
   },
   "outputs": [],
   "source": [
    "@manual{Intro8, \n",
    " title={Working With Splitters}, \n",
    " organization={DeepChem},\n",
    " author={Eastman, Peter, Mohapatra, Bibhusundar and Ramsundar, Bharath}, \n",
    " howpublished = {\\url{https://github.com/deepchem/deepchem/blob/master/examples/tutorials/Working_With_Splitters.ipynb}}, \n",
    " year={2021}, \n",
    "} "
   ]
  }
 ],
 "metadata": {
  "colab": {
   "name": "06_Going_Deeper_on_Molecular_Featurizations.ipynb",
   "provenance": []
  },
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.18"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
